﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Test_ProDivingAdmin2salmon14
{
    public class CommunicationServer
    {
        #region Data members
        private static byte[] _buffer = new byte[1024];
        private static List<Socket> _clientSockets = new List<Socket>();
        private static Socket _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        public static string sendText { get; set; } 
        #endregion

        #region Public static methods
        /// <summary>
        ///  Setting up server, Binds the server to the local IP.
        ///  Says that the server socket takes in new connections to it, by calling an event everytime it gets a new client connected to it.
        /// </summary>
        public static void SetupServer()
        {
            Console.WriteLine("Setting up Server....");
            _serverSocket.Bind(new IPEndPoint(IPAddress.Parse("192.168.1.106"), 100));
            _serverSocket.Listen(5);
            _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }

        /// <summary>
        /// Function sends data to all the sockets in the clientsocket list.
        /// </summary>
        public static void SendDive()
        {
            byte[] data = Encoding.ASCII.GetBytes(sendText);
            foreach (var socket in _clientSockets)
            {
                try
                {
                    socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
                }
                catch (SocketException e)
                {
                    System.Console.WriteLine(e.Message);
                    RemoveDC();
                }
            }
        } 
        #endregion

        #region Private static methods
        /// <summary>
        /// Function gets called everytime someone connects to the serversocket.
        /// Sets up an event everytime the clientsocket sends something to the serversocket.
        /// And adds the connecting socket to it's clientsocket list.
        /// </summary>
        /// <param name="AR"></param>
        private static void AcceptCallback(IAsyncResult AR)
        {
            Socket socket = _serverSocket.EndAccept(AR);
            _clientSockets.Add(socket);
            Console.WriteLine("Client connected");
            socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket);
            _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }

        /// <summary>
        /// Function gets called everytime a connected client sends data to the serversocket.
        /// It receives data and also sends back a reply to the client.
        /// </summary>
        /// <param name="AR"></param>
        private static void ReceiveCallback(IAsyncResult AR)
        {
            try
            {
                Socket socket = (Socket)AR.AsyncState;
                int received = socket.EndReceive(AR);

                byte[] dataBuff = new byte[received];
                Array.Copy(_buffer, dataBuff, received);

                string text = Encoding.ASCII.GetString(dataBuff);


                string response = "";
                if (text.ToLower().Contains(","))
                {
                    Contest.judgeScores.Add(text);
                    response = "!Score sent";
                }
                else if (text.ToLower().Contains(':'))
                {
                    response = "!" + Contest.GetJudgeNameByID(Int32.Parse(text.Trim(':')));
                }
                else if (text.ToLower() == "ping") 
                {
                    response = "!CONNECTED";
                }
                else
                {
                    response = "!ERROR";
                }
                System.Console.WriteLine("Server text received: " + text);

                byte[] data = Encoding.ASCII.GetBytes(response);
                socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
                socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket);
            }
            catch (SocketException e)
            {                
                System.Console.WriteLine(e.Message);
                RemoveDC();
            }

        }

        /// <summary>
        /// Function refreshes the list of connected sockets, removes the sockets that have dissconnected.
        /// </summary>
        private static void RemoveDC()
        {
            foreach (var socket in _clientSockets)
            {
                if (!IsSocketConnected(socket))
                {
                    _clientSockets.Remove(socket);
                    break;
                }
            }
        }

        /// <summary>
        /// Checks if a socket is connected or not.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        private static bool IsSocketConnected(Socket s)
        {
            bool part1 = s.Poll(1000, SelectMode.SelectRead);
            bool part2 = (s.Available == 0);

            if ((part1 && part2) || !s.Connected)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// Function event gets called when the serversocket sends data to a connected client socket.
        /// </summary>
        /// <param name="AR"></param>
        private static void SendCallback(IAsyncResult AR)
        {
            Socket socket = (Socket)AR.AsyncState;
            socket.EndSend(AR);
        } 
        #endregion
    }
}
